#include "StdAfx.h"
#include "Camera.h"
#include <iostream>

const float Camera::DEFAULT_FIRST_PERSON_OFFSET = 2.5f;
const float Camera::DEFAULT_TOP_DOWN_OFFSET = 200.0f;
const float Camera::DEFAULT_ROTATION_SPEED = 0.01f;
const float Camera::DEFAULT_FOVX = 60.0f;
const float Camera::DEFAULT_ZNEAR = 0.1f;
const float Camera::DEFAULT_ZFAR = 5000.0f;

const D3DXVECTOR3 Camera::WORLD_XAXIS(1.0f, 0.0f, 0.0f);
const D3DXVECTOR3 Camera::WORLD_YAXIS(0.0f, 1.0f, 0.0f);
const D3DXVECTOR3 Camera::WORLD_ZAXIS(0.0f, 0.0f, 1.0f);

/******************************************************************
Purpose: Camera constructor
Descripton: 
Return:
******************************************************************/
Camera::Camera(void) :
	m_pos(0.0f, 0.0f, 0.0f),
	mode(CAM_MODE_FIRST_PERSON),
	m_rotationSpeed(DEFAULT_ROTATION_SPEED),
	m_fovx(DEFAULT_FOVX),
	m_aspectRatio(0.0f),
	m_znear(DEFAULT_ZNEAR),
	m_zfar(DEFAULT_ZFAR)
{
    m_pos = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
	m_AxisX = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
    m_AxisY = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
    m_AxisZ = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
    m_viewDir = D3DXVECTOR3(0.0f, 0.0f, 1.0f);

	m_acceleration = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
    m_velocity = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
	m_curVelocity = D3DXVECTOR3(0.0f, 0.0f, 0.0f);

    D3DXQuaternionIdentity(&m_orientation);
    D3DXMatrixIdentity(&m_viewMatrix);
    D3DXMatrixIdentity(&m_projMatrix);
    D3DXMatrixIdentity(&m_viewProjMatrix);
}

/******************************************************************
Purpose: Camera destructor
Descripton: 
Return:
******************************************************************/
Camera::~Camera(void)
{
}

void Camera::setMode(CameraMode mode)
{
	this->mode = mode;
	// switch from fps to top down
}

void Camera::switchMode()
{
	OutputDebugString("switch Mode: ");
	if (mode == CAM_MODE_FIRST_PERSON)
	{
		mode = CAM_MODE_TOPDOWN;
		D3DXVECTOR3 oldPos = getPosition();
		setPosition(D3DXVECTOR3(m_pos.x, m_pos.y + DEFAULT_TOP_DOWN_OFFSET, m_pos.z));
		lookAt(m_pos, oldPos, WORLD_XAXIS);
		OutputDebugString("FPS to TopDown\n");
	}
	else
	{
		mode = CAM_MODE_FIRST_PERSON;
		D3DXVECTOR3 oldPos = getPosition();
		setPosition(D3DXVECTOR3(m_pos.x, m_pos.y - DEFAULT_TOP_DOWN_OFFSET, m_pos.z));
		lookAt(m_pos, m_pos + m_viewDir, WORLD_XAXIS);
		OutputDebugString("TopDown to FPS\n");
	}
	updateViewMatrix();
}

bool Camera::isFirstPerson() { return mode == CAM_MODE_FIRST_PERSON; }
bool Camera::isTopDown() { return mode == CAM_MODE_TOPDOWN; }

/******************************************************************
Purpose: Look at the target vector.
Descripton: 
Return: 0 - success
******************************************************************/
void Camera::lookAt(const D3DXVECTOR3 &target) { lookAt(m_pos, target, m_AxisY); }


/******************************************************************
Purpose: 
Descripton: 
Return: 0 - success
******************************************************************/
void Camera::lookAt(const D3DXVECTOR3 &eye, const D3DXVECTOR3 &target, const D3DXVECTOR3 &up)
{
    m_pos = eye;
    m_AxisZ = target - eye;
    D3DXVec3Normalize(&m_AxisZ, &m_AxisZ);

    m_viewDir = m_AxisZ;

    D3DXVec3Cross(&m_AxisX, &up, &m_AxisZ);
    D3DXVec3Normalize(&m_AxisX, &m_AxisX);

    D3DXVec3Cross(&m_AxisY, &m_AxisZ, &m_AxisX);
    D3DXVec3Normalize(&m_AxisY, &m_AxisY);
    D3DXVec3Normalize(&m_AxisX, &m_AxisX);

    D3DXMatrixIdentity(&m_viewMatrix);

    m_viewMatrix(0,0) = m_AxisX.x;
    m_viewMatrix(1,0) = m_AxisX.y;
    m_viewMatrix(2,0) = m_AxisX.z;
    m_viewMatrix(3,0) = -D3DXVec3Dot(&m_AxisX, &eye);

    m_viewMatrix(0,1) = m_AxisY.x;
    m_viewMatrix(1,1) = m_AxisY.y;
    m_viewMatrix(2,1) = m_AxisY.z;
    m_viewMatrix(3,1) = -D3DXVec3Dot(&m_AxisY, &eye);

    m_viewMatrix(0,2) = m_AxisZ.x;
    m_viewMatrix(1,2) = m_AxisZ.y;
    m_viewMatrix(2,2) = m_AxisZ.z;
    m_viewMatrix(3,2) = -D3DXVec3Dot(&m_AxisZ, &eye);

    m_viewProjMatrix = m_viewMatrix * m_projMatrix;
    D3DXQuaternionRotationMatrix(&m_orientation, &m_viewMatrix);
}


/******************************************************************
Purpose: Displace the camera by dx, dy, dz
Descripton: 
Return: 0 - success
******************************************************************/
void Camera::move(float dx, float dy, float dz)
{
    D3DXVECTOR3 eye = m_pos;
    D3DXVECTOR3 forwards;

	if (mode == CAM_MODE_FIRST_PERSON || mode == CAM_MODE_TOPDOWN)
    {
        // Calculate the forwards direction. Can't just use the camera's local
        // z axis as doing so will cause the camera to move more slowly as the
        // camera's view approaches 90 degrees straight up and down.
		D3DXVec3Cross(&forwards, &m_AxisX, &WORLD_YAXIS);
        D3DXVec3Normalize(&forwards, &forwards);
    }
    else
    {
        forwards = m_viewDir;
    }

    eye += m_AxisX * dx;
    eye += WORLD_YAXIS * dy;
    eye += forwards * dz;

    setPosition(eye);
}


/******************************************************************
Purpose: move in direction and by some amount.
Descripton: 
Return: 0 - success
******************************************************************/
void Camera::move(const D3DXVECTOR3 &direction, const D3DXVECTOR3 &amount)
{
	m_pos.x += direction.x * amount.x;
    m_pos.y += direction.y * amount.y;
    m_pos.z += direction.z * amount.z;

    updateViewMatrix();
}

/******************************************************************
Purpose: rotate the camera by heading, pitch and roll
Descripton: 
Return: 0 - success
******************************************************************/
void Camera::rotate(float headingDeg, float pitchDeg, float rollDeg)
{
	D3DXQUATERNION rot;
	float heading = D3DXToRadian(headingDeg);
	float pitch = D3DXToRadian(pitchDeg);
	float roll = D3DXToRadian(rollDeg);

	switch(mode)
	{
		default:
			break;

		case CAM_MODE_TOPDOWN:
			

		case CAM_MODE_FIRST_PERSON:
			// Rotate camera about the world y axis, Note the order the quaternions are multiplied.
			if (heading != 0.0f)
			{
				D3DXQuaternionRotationAxis(&rot, &WORLD_YAXIS, heading);
				D3DXQuaternionMultiply(&m_orientation, &rot, &m_orientation);
			}

			// Rotate camera about its local x axis.
			if (pitch != 0.0f)
			{
				D3DXQuaternionRotationAxis(&rot, &WORLD_XAXIS, pitch);
				D3DXQuaternionMultiply(&m_orientation, &m_orientation, &rot);
			}
			break;
	}
}


// Set the Camera parameters
void Camera::setPosition(const D3DXVECTOR3 &newPos)
{
    m_pos.x = newPos.x;
    m_pos.y = newPos.y;
    m_pos.z = newPos.z;
    updateViewMatrix();
}

void Camera::setOrientation(const D3DXQUATERNION &newOrientation)
{
    D3DXMATRIX m;
    D3DXMatrixRotationQuaternion(&m, &newOrientation);

    m_orientation = newOrientation;
    if (mode == CAM_MODE_FIRST_PERSON) lookAt(m_pos, m_pos + m_viewDir, WORLD_YAXIS);
    updateViewMatrix();
}

void Camera::setPerspective(float fovx, float aspect, float znear, float zfar)
{
    // Construct a projection matrix based on the horizontal field of view
    // 'fovx' rather than the more traditional vertical field of view 'fovy'.
    float e = 1.0f / tanf(D3DXToRadian(fovx) / 2.0f);
    float aspectInv = 1.0f / aspect;
    float fovy = 2.0f * atanf(aspectInv / e);
    float xScale = 1.0f / tanf(0.5f * fovy);
    float yScale = xScale / aspectInv;

    m_projMatrix(0,0) = xScale;
    m_projMatrix(1,0) = 0.0f;
    m_projMatrix(2,0) = 0.0f;
    m_projMatrix(3,0) = 0.0f;

    m_projMatrix(0,1) = 0.0f;
    m_projMatrix(1,1) = yScale;
    m_projMatrix(2,1) = 0.0f;
    m_projMatrix(3,1) = 0.0f;

    m_projMatrix(0,2) = 0.0f;
    m_projMatrix(1,2) = 0.0f;
    m_projMatrix(2,2) = zfar / (zfar - znear);
    m_projMatrix(3,2) = -znear * zfar / (zfar - znear);

    m_projMatrix(0,3) = 0.0f;
    m_projMatrix(1,3) = 0.0f;
    m_projMatrix(2,3) = 1.0f;
    m_projMatrix(3,3) = 0.0f;

    m_viewProjMatrix = m_viewMatrix * m_projMatrix;
    
    m_fovx = fovx;
    m_aspectRatio = aspect;
    m_znear = znear;
    m_zfar = zfar;
}


// Reconstruct the view matrix.
void Camera::updateViewMatrix()
{
    D3DXQuaternionNormalize(&m_orientation, &m_orientation);
    D3DXMatrixRotationQuaternion(&m_viewMatrix, &m_orientation);

    m_AxisX = D3DXVECTOR3(m_viewMatrix(0,0), m_viewMatrix(1,0), m_viewMatrix(2,0));
    m_AxisY = D3DXVECTOR3(m_viewMatrix(0,1), m_viewMatrix(1,1), m_viewMatrix(2,1));
    m_AxisZ = D3DXVECTOR3(m_viewMatrix(0,2), m_viewMatrix(1,2), m_viewMatrix(2,2));
    m_viewDir = m_AxisZ;

    m_viewMatrix(3,0) = -D3DXVec3Dot(&m_AxisX, &m_pos);
    m_viewMatrix(3,1) = -D3DXVec3Dot(&m_AxisY, &m_pos);
    m_viewMatrix(3,2) = -D3DXVec3Dot(&m_AxisZ, &m_pos);

    m_viewProjMatrix = m_viewMatrix * m_projMatrix;
}


void Camera::updatePosition(const D3DXVECTOR3 &direction, float elapsedTimeSec)
{
    if (D3DXVec3LengthSq(&m_curVelocity) != 0.0f)
    {
		float adjustedTime = elapsedTimeSec/25.0f; // do a bit of scaling as we receive a long for time

        // Only move the camera if the velocity vector is not of zero length.
        D3DXVECTOR3 displacement = (m_curVelocity * adjustedTime);

		// clamp the directions to avoid floating point rounding errors accumulating
        if (direction.x == 0.0f && fabsf(m_curVelocity.x) < 1e-6f) displacement.x = 0.0f;
        if (direction.y == 0.0f && fabsf(m_curVelocity.y) < 1e-6f) displacement.y = 0.0f;
        if (direction.z == 0.0f && fabsf(m_curVelocity.z) < 1e-6f) displacement.z = 0.0f;
        move(displacement.x, displacement.y, displacement.z);
    }
    updateVelocity(direction, elapsedTimeSec);
}

// return a new vector which represents the displacement after applying the direction and elapsed time.
const D3DXVECTOR3 Camera::getDisplacement(const D3DXVECTOR3 &direction, float elapsedTimeSec) const
{
	D3DXVECTOR3 retVec = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
    if (D3DXVec3LengthSq(&m_curVelocity) != 0.0f)
    {
		float adjustedTime = elapsedTimeSec/25.0f; // do a bit of scaling as we receive a long for time

        // Only move the camera if the velocity vector is not of zero length.
        retVec = (m_curVelocity * adjustedTime);

		// clamp the directions to avoid floating point rounding errors accumulating
        if (direction.x == 0.0f && fabsf(m_curVelocity.x) < 1e-6f) retVec.x = 0.0f;
        if (direction.y == 0.0f && fabsf(m_curVelocity.y) < 1e-6f) retVec.y = 0.0f;
        if (direction.z == 0.0f && fabsf(m_curVelocity.z) < 1e-6f) retVec.z = 0.0f;
    }
    return retVec;
}

void Camera::setAcceleration(const D3DXVECTOR3 &acceleration) { m_acceleration = acceleration; }
void Camera::setVelocity(const D3DXVECTOR3 &velocity) { m_velocity = velocity; }
void Camera::setVelocity(float x, float y, float z) {
    m_velocity.x = x;
    m_velocity.y = y;
    m_velocity.z = z;
}
void Camera::setCurrentVelocity(const D3DXVECTOR3 &currentVelocity)
{
    m_curVelocity.x = currentVelocity.x;
    m_curVelocity.y = currentVelocity.y;
    m_curVelocity.z = currentVelocity.z;
}
void Camera::setCurrentVelocity(float x, float y, float z)
{
    m_curVelocity.x = x;
    m_curVelocity.y = y;
    m_curVelocity.z = z;
}


void Camera::updateVelocity(const D3DXVECTOR3 &direction, float elapsedTimeSec)
{
	// x range
    if (direction.x != 0.0f)
    {
		m_curVelocity.x = direction.x * m_velocity.x;
    }
    else
    {
		m_curVelocity.x = 0.0f;
    }

	// y range
    if (direction.y != 0.0f)
    {
		m_curVelocity.y = direction.y * m_velocity.x;
    }
    else
    {
		m_curVelocity.y = 0.0f;
    }

	// z range
    if (direction.z != 0.0f)
    {
		m_curVelocity.z = direction.z * m_velocity.z;
    }
    else
    {
		m_curVelocity.z = 0.0f;
    }
}
